Учебный курс: Подготовка на 1С:Специалист по платформе 1С:Предприятие 8.3

Общие приемы и механизмы решения задач – Тема № 12:
Что такое «Проблема копеек» и как ее решить

Упоминание о существовании некой «проблемы копеек» встречается в требованиях к экзамену в списке наиболее частых ошибок, характерных для любой учетной задачи:

Фрагмент из списка часто встречающихся ошибок

Рисунок 1 – Фрагмент из списка часто встречающихся ошибок

Что же такое «проблема копеек»?

В задачах списания товаров «проблема копеек» выражается в следующем: при полном списании товара по количеству его себестоимость не списывается в «ноль». Остается несколько копеек. Связано это с округлениями при расчете себестоимости списания.

Видим, что следствием нерешенной «проблемы копеек» является еще и другая ошибка: не будут выведены одновременно в ноль все ресурсы регистра остатков. За такую ошибку на экзамене снижают от 0,5 до 3 баллов.

Пример: на остатке есть 3 ручки общей стоимостью 100,00 руб. Спишем 3 ручки. Расчет себестоимости списания выполним по средней.

Формула: Себестоимость Остаток / Количество Остаток * Количество Списания

Себестоимость единицы товара составит 100 / 3 = 33,33 руб.

Себестоимость списания согласно формуле составит 33,33 * 3 = 99,99 руб.

Итого после списания останется 0 шт по количеству и 1 копейка по стоимости.

Возникает вопрос: зачем рассчитывать себестоимость за единицу и уж тем более ее округлять? Давайте сразу применим формулу без промежуточных округлений.

Вычисления система будет выполнять последовательно:

100 / 3*3 = 33.33333…* 3 = 99.99999…

Ресурсы для хранения показателей в денежном выражении имеют точность два знака после запятой. При записи значения в ресурс регистра произойдет округление по математическим правилам. Записано будет 100,00 руб.

Казалось бы, все хорошо. Но в таком варианте на экзамене «проблема копеек» будет считаться нерешенной. Почему? Из-за тех самых 99.999999. Правильный результат в данном случае был получен в результате автоматического округления.

Как решить «проблему копеек»?

Если на результат влияет порядок операндов, давайте его изменим.

Вариант формулы № 2: Количество списания / Количество остаток x Сумма остаток

Себестоимость списания трех ручек из примера составит 3 / 3 * 100 = 1 * 100 = 100 руб.

Вариант формулы № 3: Сумма остаток x Количество списания / Количество остаток.

На данных из примера получается: 100 * 3 / 3 = 300 / 3 = 100.

Видим, что в обоих случаях автоматическое округление при записи в регистр выполняться не будет.

Означает ли это, что, поменяв местами операнды, мы решили «проблему копеек»?

Ответ: не во всех случаях. Есть ограничение – «проблема копеек» будет решена только в том случае, если на последнем списании значения переменных КоличествоОстаток и КоличествоСписания будут равны. Тогда на последнем списании формула № 2 превращается в вариант:

Количество остаток / Количество остаток x Сумма остаток = 1 * Сумма остаток = Сумма остаток.

В таком случае решение по формуле с правильной последовательностью операндов принимается на экзамене. Но на очной аттестации нужно быть готовым доказать, почему в случае Вашей задачи такой метод подходит.

Далее при разборе практического примера увидим, что с определением момента последнего списания не всегда все так просто.

Надежный способ решения «проблемы копеек»

Суть метода проста: при последнем списании товара списываем весь остаток стоимости, не применяя формулу для расчета суммы списания.

Именно этот метод рекомендуется использовать на аттестации.

Как определить, что списание последнее? При последнем списании количество остатка совпадет с количеством списания.

Усовершенствованная формула для расчета себестоимости списания будет выглядеть так:

Если КоличествоСписания = КоличествоОстаток Тогда СебестоимостьСписания = СебестоимостьОстаток Иначе СебестоимостьСписания = СебестоимостьОстаток*КоличествоСписания/КоличествоОстаток КонецЕсли;

или так:

СебестоимостьСписания=?(КоличествоСписания = КоличествоОстаток, СебестоимостьОстаток, СебестоимостьОстаток*КоличествоСписания/КоличествоОстаток);

Будем называть эту формулу формулой с учетом последнего списания.

Как обстоят дела с использованием этой формулы? Просто везде и всегда в алгоритме вставляем ее и считаем, что «проблема копеек» у нас теперь заведомо решена?

Нет. Есть все тот же нюанс в определении момента последнего списания. Но в этой формуле ничто не мешает вставить в конструкцию «Если…» другое условие для определения момента последнего списания.

Практический пример расчета себестоимости по средней с решением «проблемы копеек»

Постановка задачи:

Складской учет товаров не ведется. Приход товаров оформляется документом «Приходная накладная». Расход товаров оформляется документом «Расходная накладная». Себестоимость списания определяется как средняя по номенклатурной позиции».

Фраза в условии «Складской учет товаров не ведется» означает, что приход и расход товаров (количественный учет) не ведется в разрезе складов. Учет по количеству ведется только в разрезе номенклатуры.

Нам понадобятся объекты метаданных:

  • справочник «Номенклатура»
  • документ «Приходная накладная»
  • документ «Расходная накладная».

Эти объекты в каркасной конфигурации уже есть. Для решения задачи они полностью подходят.

Для расчета себестоимости списания номенклатуры нужно знать остаток себестоимости и остаток по количеству. Для этих целей понадобится регистр накопления. Вид регистра – «Остатки», т.к. интересуют именно остатки стоимости и количества. В регистре должно быть одно измерение Номенклатура, т.к. остатки нужны в разрезе номенклатуры. И должно быть два числовых ресурса – Количество и Сумма.

В каркасной конфигурации есть РН «ОстаткиНоменклатуры».

Измерение «Номенклатура» в нем есть, установим для него свойство Запрет незаполненных значений в истину, т.к. движения по регистру с пустой номенклатурой не имеют смысла.

Ресурс Количество есть. Добавим ресурс Сумма (Число 12, 2):

Структура РН «Остатки номенклатуры»

Рисунок 2 – Структура РН «Остатки номенклатуры»

Регистраторы:

  • «Приходная накладная»
  • «Расходная накладная»:

Регистраторы РН «Остатки номенклатуры»

Рисунок 3 – Регистраторы РН «Остатки номенклатуры

Обработка проведения для документа «Приходная накладная»

С помощью конструктора движений необходимо сформировать движения документа «Приходная накладная» по регистру накопления «Остатки номенклатуры» с видом Приход. Как это сделать, подробно рассмотрено в главе 6. Как реализовать поступление товаров в компанию.

Обработка проведения для документа «Расходная накладная»

Для документа «Расходная накладная» обойтись возможностями конструктора движений не получится, потому что данных из табличной части документа недостаточно для формирования движений по списанию товаров. Понадобятся еще данные об остатках количества и себестоимости по РН «Остатки номенклатуры».

Создадим процедуру ОбработкаПроведения в модуле объекта и заполним ее вручную.

Примечание: При решении данной задачи ограничимся только решением задачи списания себестоимости и «проблемы копеек». Контроль отрицательных остатков, установку блокировки в рамках данного решения рассматривать и выполнять не будем. Про блокировки можно узнать в главе 11. Какие навыки в использовании управляемых блокировок потребуются на экзамене, про контроль отрицательных остатков – см. главу 9. Как выполнить контроль остатков по старой методике.

Листинг обработки проведения:

В коде преднамеренно закомментированы некоторые строки. Позже разберем последствия.

Процедура ОбработкаПроведения(Отказ, РежимПроведения) // 1. Подготовка наборов записей регистра Движения.ОстаткиНоменклатуры.Записывать = Истина; Движения.Записать(); // 2. Установка маркера Записи у регистра Движения.ОстаткиНоменклатуры.Записывать = Истина; // 3. Запрос для получения данных для расчета себестоимости Запрос = новый Запрос("ВЫБРАТЬ | РасходнаяНакладнаяСписокНоменклатуры.Номенклатура КАК Номенклатура, // | СУММА(РасходнаяНакладнаяСписокНоменклатуры.Количество) КАК Количество | РасходнаяНакладнаяСписокНоменклатуры.Количество КАК Количество |ПОМЕСТИТЬ ТЧСписокНоменклатуры |ИЗ | Документ.РасходнаяНакладная.СписокНоменклатуры КАК РасходнаяНакладнаяСписокНоменклатуры |ГДЕ | РасходнаяНакладнаяСписокНоменклатуры.Ссылка = &Ссылка | // |СГРУППИРОВАТЬ ПО // | РасходнаяНакладнаяСписокНоменклатуры.Номенклатура | |ИНДЕКСИРОВАТЬ ПО | Номенклатура |; | |//////////////////////////////////////////////////////////////////////////////// |ВЫБРАТЬ | ТЧСписокНоменклатуры.Номенклатура, | ТЧСписокНоменклатуры.Количество, | ЕСТЬNULL(ОстаткиНоменклатурыОстатки.СуммаОстаток, 0) КАК СуммаОстаток, | ЕСТЬNULL(ОстаткиНоменклатурыОстатки.КоличествоОстаток, 0) КАК КоличествоОстаток |ИЗ | ТЧСписокНоменклатуры КАК ТЧСписокНоменклатуры | ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ОстаткиНоменклатуры.Остатки( | &МоментВремени, | Номенклатура В | (ВЫБРАТЬ | ТЧСписокНоменклатуры.Номенклатура | ИЗ | ТЧСписокНоменклатуры КАК ТЧСписокНоменклатуры)) КАК ОстаткиНоменклатурыОстатки | ПО ТЧСписокНоменклатуры.Номенклатура = ОстаткиНоменклатурыОстатки.Номенклатура"); //4. Заполнение параметров запроса Запрос.УстановитьПараметр("МоментВремени", МоментВремени()); Запрос.УстановитьПараметр("Ссылка", Ссылка); //5. выполняем запрос, обходим выборку, рассчитываем себестоимость и формируем движения Результат = Запрос.Выполнить(); ВыборкаНоменклатура = Результат.Выбрать(); Пока ВыборкаНоменклатура.Следующий() Цикл КоличествоСписания = ВыборкаНоменклатура.Количество; КоличествоОстаток = ВыборкаНоменклатура.КоличествоОстаток; СуммаОстаток = ВыборкаНоменклатура.СуммаОстаток; Движение = Движения.ОстаткиНоменклатуры.ДобавитьРасход(); Движение.Период = Дата; Движение.Номенклатура = ВыборкаНоменклатура.Номенклатура; Движение.Количество = КоличествоСписания; //6. расчет себестоимости списания по формулам с разным порядком операндов ВариантРасчета1 = СуммаОстаток/КоличествоОстаток*КоличествоСписания; Сообщить("Вариант 1: СуммаОстаток/КоличествоОстаток*КоличествоСписания ="+строка(ВариантРасчета1)); Движение.Сумма = ВариантРасчета1; Сообщить("Вариант 1: Сумма в регистре ="+строка(Движение.Сумма)); ВариантРасчета2 = КоличествоСписания/КоличествоОстаток*СуммаОстаток; Сообщить("Вариант 2: КоличествоСписания/КоличествоОстаток*СуммаОстаток ="+строка(ВариантРасчета2)); Движение.Сумма = ВариантРасчета2; Сообщить("Вариант 2: Сумма в регистре ="+строка(Движение.Сумма)); ВариантРасчета3 = СуммаОстаток*КоличествоСписания/КоличествоОстаток; Сообщить("Вариант 3: СуммаОстаток*КоличествоСписания/КоличествоОстаток ="+строка(ВариантРасчета3)); Движение.Сумма = ВариантРасчета3; Сообщить("Вариант 3: Сумма в регистре ="+строка(Движение.Сумма)); //7. списание полного остатка стоимости при последнем списании ВариантРасчета4=?(КоличествоСписания = КоличествоОстаток,СуммаОстаток, СуммаОстаток*КоличествоСписания/КоличествоОстаток); Сообщить("Вариант 4: метод с контролем последнего списания ="+строка(ВариантРасчета4)); Движение.Сумма = ВариантРасчета4; Сообщить("Вариант 4: Сумма в регистре ="+строка(Движение.Сумма)); КонецЦикла; КонецПроцедуры

Разберем ключевые точки алгоритма

Подготовка регистров к работе (п. 1, 2)

  1. Очищаем движения, чтобы при перепроведении документа не учитывались старые движения документа. Это происходит, когда дата документа при перепроведении сдвигается вперед. Для очистки старых движений документа используем принудительную запись пустого набора. В этом случае при чтении данных из регистра старые движения документа в расчет виртуальных таблиц не попадут.
  2. Устанавливаем маркер записи движений в значение Истина, чтобы при проведении документа записались сформированные в обработке проведения движения. В этом случае в конце процедуры обработки проведения использовать метод Движения.Записать() не нужно.

Запрос получения данных для расчета себестоимости (п. 3)

Первый запрос пакета – это получение данных из табличной части документа.

  • Внимание: группировка строк по номенклатуре закомментирована преднамеренно! Позже разберем, к чему это приведет.
  • В списке полей выбора оставляем только Номенклатуру и Количество. Для расчета себестоимости списания нужны только они. Представление номенклатуры не выводим, т.к. не планируем выводить его в сообщениях и т.п.
  • Устанавливаем отбор по Ссылке для отбора данных только по нашему документу.
  • Для оптимизации производительности индексируем поле Номенклатура. Далее по нему будем соединяться с другой таблицей.
  • Результат помещаем во временную таблицу ТЧСписокНоменклатуры.

Второй запрос пакета

Источниками данных для второго запроса послужат:

  • Временная таблица ТЧСписокНоменклатуры, подготовленная первым запросом
  • Виртуальная таблица остатков РН «ОстаткиНоменклатуры» (ОстаткиНоменклатурыОстатки).
  • Будем использовать следующие параметры виртуальной таблицы остатков:

    • Период – это значение параметра МоментВремени
    • Условие – ограничиваем выборку только номенклатурой из временной таблицы ТЧСписокНоменклатуры. Интересуют остатки только по этой номенклатуре:

Параметры виртуальной таблицы остатков РН «Остатки номенклатуры»

Рисунок 4 – Параметры виртуальной таблицы остатков
РН «Остатки номенклатуры»

Временную таблицу ТЧСписокНоменклатуры соединяем с таблицей ОстаткиНоменклатурыОстатки по полю Номенклатура. Используем левое соединение на случай, если соответствующих записей в таблице остатков не будет. По этой же причине используем функцию ЕстьNULL() применительно к полям выбора из регистра остатков.

Заполнение параметров запроса (п. 4)

Для заполнения параметра Момент времени будем использовать момент времени документа.

МоментВремени рассмотрен в главе 3. Что такое момент времени и для чего он используется.

Цикл по выборке из запроса (п. 5)

Обходим выборку из результата запроса и на каждую строку выборки создаем движение по расходу по РН «Остатки номенклатуры». Внутри цикла выполняем расчет себестоимости.

Расчет себестоимости и формирование движений (п. 6)

В алгоритме приведены все варианты построения формулы для расчета себестоимости списания, которые разбирались в теоретической части. Цель – на практике посмотреть, как влияет перестановка операндов на результат. При решении задачи на экзамене этот фрагмент, конечно же, не нужен.

Применение формулы, рекомендуемой на экзамене (п. 7)

Последний вариант – это как раз вариант, рекомендуемый на экзамене. Т.к. этот вариант расчета выполняется последним в списке формул, то в регистр будет записан результат расчета именно по этой формуле.

Проверка результата в режиме «1С:Предприятие»

Создадим документ «Приходная накладная»:

Документ «Приходная накладная № 1»

Рисунок 5 – Документ «Приходная накладная № 1»

Проверим движения по РН «Остатки номенклатуры»:

Движения документа «Приходная накладная № 1»

Рисунок 6 – Движения документа «Приходная накладная № 1»

Приход сформирован. Займемся списанием товаров.

Создадим документ «Расходная накладная». Выполним в нем списание всех трех ручек, которые имеются на остатке.

 Документ «Расходная накладная № 1»

Рисунок 7 – Документ «Расходная накладная № 1»

При проведении получим следующие сообщения:

 Сообщения при проведении документа «Расходная накладная № 1»

Рисунок 8 – Сообщения при проведении документа «Расходная накладная № 1»

Полученные результаты вполне ожидаемы и подтверждают теоретические изыскания.

Если заглянуть в движения по РН «Остатки номенклатуры», то там тоже все отлично – никаких копеек:

Движения по РН «Остатки номенклатуры»

Рисунок 9 – Движения по РН «Остатки номенклатуры»

Подведем итоги

Мы выяснили, что скрывается под названием «проблема копеек», разобрали методы ее решения, проверили их на практике, решив пример с расчетом себестоимости списания по средней.

Осталось выяснить, в чем может быть нюанс с определением момента последнего списания, о котором упоминали при разборе формул. Сделаем это в следующем блоке материалов.

Перейти к следующей теме:
“«Подводный камень» проблемы копеек: определение момента последнего списания” (№ 13)

Комментарии закрыты